package com.example.demo.service;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.parser.Feature;
import com.example.demo.consts.Consts;
import com.example.demo.dao.*;
import com.example.demo.entry.*;
import com.example.demo.util.HashUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.Base64;
import java.util.Date;
import java.util.HashSet;
import java.util.TimeZone;

@Slf4j
@Service
public class LedgerService {
    private static final Base64.Decoder decoder = Base64.getDecoder();
    private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd-hh:mm:ss.SSS");
    @Autowired
    private BlockDao blockService;
    @Autowired
    private AddressDao addressService;
    @Autowired
    private TransactionDao transactionService;
    @Autowired
    private AttachmentDao attachmentService;
    @Autowired
    private ParentsDao parentsService;
    @Autowired
    private OgService ogService;
    @Autowired
    private ProfileHistoryDao profileHistoryDao;
    @Autowired
    private KnownAssetsDao knownAssetsDao;

    @Transactional(rollbackFor = Exception.class)
    public void fullSync(int height) throws Exception {

        log.info("数据落后，开始同步块数据");
        boolean hasTx = syn_block(height);
        if (hasTx) {
            log.info("数据落后，开始同步交易数据");
            syn_transaction(height);
        }
        log.info("数据落后，开始同步性能表");
        syn_profileHistory(height);
        log.info("成功更新高度" + height);
    }

    @Transactional(rollbackFor = Exception.class)
    public void syn_profileHistory(int height) throws Exception {

        ProfileHistory profileHistory = new ProfileHistory();
        //最新区块
        profileHistory.setFiled("latest_block");
        profileHistory.setValue_int(height);
        try {
            profileHistoryDao.insert(profileHistory);
        } catch (Exception e) {
            log.error("更新性能表失败");
            throw e;
        }

        //历史峰值

        profileHistory = new ProfileHistory();
        profileHistory.setFiled("tps_max");
        Double MaxTPS = profileHistoryDao.findMaxTPS();
        if (MaxTPS == null) MaxTPS = 0.0;
        if (height > 0) {
            Block pre = blockService.findByHeight(height - 1);
            if (pre == null) {
                log.info("获取上一个块数据失败");
                throw new Exception("高度为:" + height + "情况下获取上一个块数据失败");
            }
            Block cur = blockService.findByHeight(height);
            if (pre.getOutput_time() != null && cur.getOutput_time() != null) {
                long pre_date = pre.getOutput_time().getTime();
                long cur_date = cur.getOutput_time().getTime();
                int Tx = cur.getTransaction_count();
                double tmp = (1000 * (double) Tx) / (cur_date - pre_date);
                MaxTPS = Math.max(MaxTPS, tmp);
            }
        }
        profileHistory.setValue_double(new BigDecimal(MaxTPS));
        try {
            profileHistoryDao.insert(profileHistory);
        } catch (Exception e) {
            log.error("更新性能表失败");
            throw e;
        }

        //节点
        profileHistory = new ProfileHistory();
        JSONObject jsonObject = JSONObject.parseObject(ogService.nodeNumber(), Feature.OrderedField);
        if (jsonObject != null && jsonObject.get("data") != null){
            JSONObject data = JSONObject.parseObject(jsonObject.get("data").toString());
            JSONObject bft = JSONObject.parseObject(data.get("bft").toString());
            int number = bft.getJSONArray("partners").size();
            profileHistory.setFiled("nodes");
            profileHistory.setValue_int(number);
            try {
                profileHistoryDao.insert(profileHistory);
            } catch (Exception e) {
                log.error("更新性能表失败");
                throw e;
            }
        }



        //账本大小
        profileHistory = new ProfileHistory();
        profileHistory.setFiled("ledger_size");
        jsonObject = JSONObject.parseObject(ogService.ledgerSize(), Feature.OrderedField);
        long ledgersize = Long.valueOf(jsonObject.get("data").toString());
        profileHistory.setValue_long(new BigDecimal(ledgersize));
        try {
            profileHistoryDao.insert(profileHistory);
        } catch (Exception e) {
            log.error("更新性能表失败");
            throw e;
        }


        //类型数量
        profileHistory = new ProfileHistory();
        profileHistory.setFiled("property_size");
        profileHistory.setValue_int(Math.max(transactionService.findTYPE() - 1, 0));
        try {
            profileHistoryDao.insert(profileHistory);
        } catch (Exception e) {
            log.error("更新性能表失败");
            throw e;
        }

        log.info("成功更新性能表");
    }

    @Transactional(rollbackFor = Exception.class)
    public boolean syn_block(int height) throws Exception {
        boolean hasTx = false;
        //id为高度
        String blockResponse = ogService.blockById(height);

        JSONObject jsonObject = JSONObject.parseObject(blockResponse, Feature.OrderedField);
        JSONObject data = JSONObject.parseObject(jsonObject.get("data").toString());
        // block basic info
        Block block = new Block();
        block.setHash(HashUtil.cleanHash(data.get("Hash")));
        block.setHeight(height);

        String heightTxs = ogService.txByHeight(height);
        JSONObject tmp_json = JSONObject.parseObject(heightTxs, Feature.OrderedField);
        tmp_json = JSONObject.parseObject(tmp_json.get("data").toString());
        JSONArray txHashes = tmp_json.getJSONArray("hashes");

        if (txHashes == null) {
            block.setTransaction_count(0);
        } else {
            block.setTransaction_count(txHashes.size());
            if (txHashes.size() > 0) {
                hasTx = true;
            }
        }
        String time = data.get("Timestamp") + "";
        if (time.equals("0")) {
            block.setOutput_time(null);
        } else {
            Date date = new Date(Long.parseLong(time));
            block.setOutput_time(date);
        }
        block.setMiner(HashUtil.cleanHash(data.get("Issuer")));
        block.setNonce((int) data.get("AccountNonce"));
        block.setMine_nonce((int) data.get("MineNonce"));
        block.setCommittee(1);
        /**
         * 获取上个区块的出块时间，进行差值计算后，交易数/差值
         * 测试的时候没有连续的区块会查处null值
         */
        if (height > 0) {
            // handle previous block except for genesis
            Block pre_block = blockService.findByHeight(height - 1);
            if (pre_block == null) {
                throw new Exception("获取" + data.get("Hash").toString() + "的上一个区块失败");
            }

            block.setUnion_sign(HashUtil.cleanHash(data.get("BlsJointSig")));
            block.setUnion_sign_public(HashUtil.cleanHash(data.get("BlsJointPubKey")));
            if (height > 1) {
                pre_block.setNext_hash(HashUtil.cleanHash(data.get("Hash")));
                blockService.updateNextHash(pre_block);
                block.setPrev_hash(pre_block.getHash());
            }
        }
        // we currently does not know the txs in this block
        block.setTransaction_avg_con(new BigDecimal(0));
        block.setState_root(HashUtil.cleanHash(data.get("StateRoot")));
        try {
            blockService.addBlock(block);
        } catch (Exception e) {
            log.error("插入高度：" + block.getHash() + "失败");
            throw e;
        }
        JSONArray parentArray = data.getJSONArray("ParentsHash");
        int size = (parentArray == null ? 0 : parentArray.size());
        for (int i = 0; i < size; i++) {
            Parent parent = new Parent();
            parent.setHash(HashUtil.cleanHash(block.getHash()));
            parent.setParent_hash(HashUtil.cleanHash(parentArray.get(i)));
            parent.setType(1);
            try {
                parentsService.add(parent);
            } catch (Exception e) {
                log.error("块hash：" + block.getHash() + "的父节点块插入失败");
                throw e;
            }
        }

        return hasTx;
    }

    @Transactional(rollbackFor = Exception.class)
    public void syn_transaction(int height) throws Exception {
        HashSet<String> tx = new HashSet<>();
        log.info("Syncing height " + height);
        //找出该高度下所有交易
        JSONObject tmp_json = JSONObject.parseObject(ogService.txByHeight(height), Feature.OrderedField);
        tmp_json = JSONObject.parseObject(tmp_json.get("data").toString());
        //取下sequencer，在该块中的交易都存下进库之后，再将sequencer的信息进库
        JSONObject sequencer_info_json = JSONObject.parseObject(ogService.blockById(height), Feature.OrderedField);
        sequencer_info_json = JSONObject.parseObject(sequencer_info_json.get("data").toString());
        //获取该块的出块时间
        String time = sequencer_info_json.get("Timestamp") + "";
        Date output_time = new Date(Long.parseLong(time));

        JSONArray txHashes = tmp_json.getJSONArray("hashes");
        if (txHashes != null) {
            //将交易存下
            for (Object txHash : txHashes) {
                tx.add(txHash.toString());
            }
        }

        int totalConnection = 0;
        int totalTx = tx.size();

        for (String hash : tx) {
            System.out.println(hash);
            JSONObject tmp_json1 = JSONObject.parseObject(ogService.txByHash(hash), Feature.OrderedField);
            tmp_json1 = JSONObject.parseObject(tmp_json1.get("data").toString());

            JSONArray parents = null;
            switch ((int) tmp_json1.get("type")) {
                case Consts.TX_TYPE_TX:
                    handleTx(height, output_time, hash, tmp_json1);
                    parents = ((JSONObject) tmp_json1.get("transaction")).getJSONArray("parents");
                    break;
                case Consts.TX_TYPE_SEQUENCER:
                    break;
                case Consts.TX_TYPE_CAMPAIGN:
                    break;
                case Consts.TX_TYPE_TERM_CHANGE:
                    break;
                case Consts.TX_TYPE_ARCHIVE:
                    handleArchive(height, output_time, hash, tmp_json1);
                    parents = ((JSONObject) tmp_json1.get("archive")).getJSONArray("parents");
                    break;
                case Consts.TX_TYPE_ACTION:
                    handleAction(height, output_time, hash, tmp_json1);
                    parents = ((JSONObject) tmp_json1.get("action")).getJSONArray("parents");
                    break;
            }
            totalConnection += parents.size();
        }
        Block myBlock = blockService.findByHeight(height);
        if (totalTx == 0) {
            myBlock.setTransaction_avg_con(new BigDecimal(0));
        } else {
            myBlock.setTransaction_avg_con(new BigDecimal((double) totalConnection / totalTx));
        }
        blockService.updateTransactionAvgCon(myBlock);
    }

    //发币交易
    private void handleAction(int height, Date output_time, String hash, JSONObject tmp_json1) throws Exception {
        JSONObject action = (JSONObject) tmp_json1.get("action");
        JSONObject actionData = (JSONObject) action.get("data");
        Transaction token_tx = new Transaction();
        token_tx.setHash(HashUtil.cleanHash(hash));
        token_tx.setHeight(height);
        token_tx.setType(5);
        token_tx.setOutput_time(output_time);
        token_tx.setData_size(0);

        Attachment attachment = new Attachment();
        attachment.setType(Consts.TX_TYPE_ACTION);
        attachment.setData(actionData.toString());
        try {
            attachmentService.add(attachment);
        } catch (Exception e) {
            log.error("发币交易hash:" + hash + "添加attachment信息失败");
            throw e;
        }

        token_tx.setData_id(attachment.getId());
        token_tx.setProperty_type((int) actionData.get("token_id"));
        token_tx.setAmount(0);
        token_tx.setAccount_nonce((int) action.get("account_nonce"));
        token_tx.setMine_nonce((int) action.get("mind_nonce"));
        token_tx.setSign(HashUtil.cleanHash(action.get("sign")));
        token_tx.setFrom_address(HashUtil.cleanHash(action.get("from")));
        JSONArray parents = action.getJSONArray("parents");
        token_tx.setParent_count(parents.size());
        for (Object o : parents) {
            Parent parent = new Parent();
            parent.setHash(HashUtil.cleanHash(hash));
            parent.setParent_hash(HashUtil.cleanHash(o));
            parent.setType(5);
            parentsService.add(parent);
        }
        try {
            transactionService.addtransaction(token_tx);
        } catch (Exception e) {
            log.error("failed to add transaction", e);
            throw e;
        }
        KnownAssets knownAssets = new KnownAssets();
        knownAssets.setProperty_id(new BigInteger(actionData.get("token_id").toString()));
        knownAssets.setAmount(new BigInteger(actionData.get("value").toString()));
        knownAssets.setHeight(height);
        knownAssets.setName(actionData.get("token_name") + "");
        knownAssets.setIssuer(HashUtil.cleanHash(action.get("from")));
        try {
            knownAssetsDao.insert(knownAssets);
        } catch (Exception e) {
            log.error("记录发币交易失败");
            throw e;
        }
    }

    //存证交易
    private void handleArchive(int height, Date output_time, String hash, JSONObject tmp_json1) throws Exception {
        JSONObject archive = JSONObject.parseObject(tmp_json1.get("archive").toString());
        String data_info = archive.get("data").toString();
        byte[] data_info_byte = decoder.decode(data_info);
        Transaction attachment_tx = new Transaction();
        Attachment attachment = new Attachment();
        attachment.setType(4);
        attachment.setData(new String(data_info_byte, StandardCharsets.UTF_8));
        //存证信息入库
        attachmentService.add(attachment);
        attachment_tx.setHash(HashUtil.cleanHash(hash));
        attachment_tx.setHeight(height);
        attachment_tx.setType(4);
        attachment_tx.setOutput_time(output_time);
        //计算data大小
        attachment_tx.setData_size(data_info_byte.length);
        attachment_tx.setData_id(attachment.getId());
        attachment_tx.setMine_nonce((int) archive.get("mind_nonce"));
        attachment_tx.setSign(null);
        //获取parents信息
        JSONArray parents = archive.getJSONArray("parents");
        attachment_tx.setParent_count(parents.size());
        //存正信息入库
        try {
            transactionService.addtransaction(attachment_tx);
        } catch (Exception e) {
            log.error("failed to add transaction", e);
            throw e;
        }
        //对于parent还要入库
        for (Object o : parents) {
            Parent parent = new Parent();
            parent.setHash(HashUtil.cleanHash(hash));
            parent.setParent_hash(HashUtil.cleanHash(o.toString()));
            parent.setType(0);
            parentsService.add(parent);
        }
    }

    // 转账
    private void handleTx(int height, Date output_time, String hash, JSONObject tmp_json1) throws Exception {
        JSONObject transaction_info = JSONObject.parseObject(tmp_json1.get("transaction").toString());
        Transaction transfer = new Transaction();
        transfer.setHash(HashUtil.cleanHash(hash));
        transfer.setHeight(height);
        transfer.setType(0);
        transfer.setOutput_time(output_time);
        //此两项没有
        transfer.setData_size(0);
        transfer.setData_id(null);

        transfer.setProperty_type((int) transaction_info.get("tokenId"));
        int amount = Integer.parseInt(transaction_info.get("value") + "");
        transfer.setAmount(amount);
        transfer.setMine_nonce((int) transaction_info.get("mind_nonce"));
        transfer.setSign(HashUtil.cleanHash(transaction_info.get("sign")));
        //对于地址也要入库
        transfer.setFrom_address(HashUtil.cleanHash(transaction_info.get("from")));
        transfer.setTo_address(HashUtil.cleanHash(transaction_info.get("to")));
        Address address1 = new Address();
        address1.setAddress(HashUtil.cleanHash(transaction_info.get("from")));
        Address address2 = new Address();
        address2.setAddress(HashUtil.cleanHash(transaction_info.get("to")));
        try {
            addressService.addAddress(address1);
            addressService.addAddress(address2);
        } catch (Exception e) {
            log.error("failed to add address", e);
            throw e;
        }
        JSONArray parents = transaction_info.getJSONArray("parents");
        transfer.setParent_count(parents.size());
        for (Object o : parents) {
            Parent parent = new Parent();
            parent.setHash(HashUtil.cleanHash(hash));
            parent.setParent_hash(HashUtil.cleanHash(o));
            parent.setType(0);
            parentsService.add(parent);
        }
        try {
            transactionService.addtransaction(transfer);
        } catch (Exception e) {
            log.error("failed to add transaction", e);
            throw e;
        }
    }

}
